/*  ----------------------------------------------------------------------------
 *  Arello Mobile
 *  Fast 3d Engine
 *  ----------------------------------------------------------------------------
 * Except where otherwise noted, this work is licensed under
 * http://creativecommons.org/licenses/by/3.0
 */

#include <string>
#include "Shader.h"
#include "TextureSimple.h"
#include <lib3d/anim2d/Sequence.h>
#include "TextureReflection.h"
#include "TextureRenderTarget.h"
#include <map>
#include <xml/Saver.h>
#include <xml/Stream.h>
#include <lib3d/hard/Hardware.h>
#include <safe_new.h>

#include <boost/bind.hpp>

namespace liba {
namespace lib3d {
namespace material {

const Atom Shader::transparency_name("Transparency");
const Atom Shader::specular_power_name("SpecularPower");
const Atom Shader::diffuse_name("Diffuse");
const Atom Shader::reflection_name("Reflection");
const Atom Shader::reflectivity_name("Reflectivity");
const Atom Shader::emissive_name("Emissive");
const Atom Shader::specular_name("Specular");

const Atom Shader::diffuse_texture_name("Diffuse");
const Atom Shader::bump_texture_name("Bump");
const Atom Shader::emissive_texture_name("Emissive");
const Atom Shader::cubic_reflection_texture_name("Reflection");
const Atom Shader::randomizeName("Randomize");

struct TextureParser : public xml::ObjectANSI
{
	Atom target_name;
	TextureSimple::Name texture_simple;
	anim2d::AnimatedSequences * texture_sequence;	//does not release the pointer

	TextureParser(const Atom & name)
	:	target_name(name),
		texture_sequence(0)
	{}

	virtual bool on_attribute(xml::Provider * prov, const std::string & name, const std::string & value)
	{
		return texture_simple.on_attribute(prov, name, value);
	}

	virtual bool on_node(xml::Provider * prov, const std::string & name)
	{
		if(name == "Sequences")
		{
			texture_sequence = new anim2d::AnimatedSequences(target_name);
			return prov->parse_object(texture_sequence);
		}

		return false;
	}
};

Shader::Shader()
	: triangles_count( 0 )
	, vertex_count( 0 )
	, SuperPuperMegaEmissiveAlphaMutilplyByAlphaBug( false )
	, transparency(
		boost::bind(&Shader::get_transparency, this),
		boost::bind(&Shader::set_transparency, this, _1))
	, diffuse(
		boost::bind(&Shader::GetDiffuse, this),
		boost::bind(&Shader::SetDiffuse, this, _1))
	, reflection(
		boost::bind(&Shader::GetReflection, this),
		boost::bind(&Shader::SetReflection, this, _1))
	, emissive(
		boost::bind(&Shader::GetEmissive, this),
		boost::bind(&Shader::SetEmissive, this, _1))
{
	this->RegisterProperty(transparency_name, &this->transparency);
	this->RegisterProperty(diffuse_name, &this->diffuse);
	this->RegisterProperty(reflection_name, &this->reflection);
	this->RegisterProperty(emissive_name, &this->emissive);
	//this->randomize.Invoked().
	this->RegisterCommand(randomizeName, &this->randomize);
}

Shader::Shader(const Atom & name)
	: animate::Node( name )
	, triangles_count( 0 )
	, vertex_count( 0 )
	, SuperPuperMegaEmissiveAlphaMutilplyByAlphaBug( false )
	, transparency(
		boost::bind(&Shader::get_transparency, this),
		boost::bind(&Shader::set_transparency, this, _1))
	, diffuse(
		boost::bind(&Shader::GetDiffuse, this),
		boost::bind(&Shader::SetDiffuse, this, _1))
	, reflection(
		boost::bind(&Shader::GetReflection, this),
		boost::bind(&Shader::SetReflection, this, _1))
	, emissive(
		boost::bind(&Shader::GetEmissive, this),
		boost::bind(&Shader::SetEmissive, this, _1))
{
	this->RegisterProperty(transparency_name, &this->transparency);
	this->RegisterProperty(diffuse_name, &this->diffuse);
	this->RegisterProperty(reflection_name, &this->reflection);
	this->RegisterProperty(emissive_name, &this->emissive);
}

Shader::~Shader()
{}

Shader::Shader(const Shader & other)
	: animate::Node(other)
	, material(other.material)
	, SuperPuperMegaEmissiveAlphaMutilplyByAlphaBug(other.SuperPuperMegaEmissiveAlphaMutilplyByAlphaBug)
	, triangles_count(other.triangles_count)
	, vertex_count(other.vertex_count)
	, transparency(
		boost::bind(&Shader::get_transparency, this),
		boost::bind(&Shader::set_transparency, this, _1))
	, diffuse(
		boost::bind(&Shader::GetDiffuse, this),
		boost::bind(&Shader::SetDiffuse, this, _1))
	, reflection(
		boost::bind(&Shader::GetReflection, this),
		boost::bind(&Shader::SetReflection, this, _1))
	, emissive(
		boost::bind(&Shader::GetEmissive, this),
		boost::bind(&Shader::SetEmissive, this, _1))
{
	this->RegisterProperty(transparency_name, &this->transparency);
	this->RegisterProperty(diffuse_name, &this->diffuse);
	this->RegisterProperty(reflection_name, &this->reflection);
	this->RegisterProperty(emissive_name, &this->emissive);

	if(other.diffuse_texture)
        diffuse_texture.reset(other.diffuse_texture->Clone());
	if(other.bump_texture)
		bump_texture.reset(other.bump_texture->Clone());
	if(other.emissive_texture)
		emissive_texture.reset(other.emissive_texture->Clone());
	if(other.cubic_reflection_texture)
		cubic_reflection_texture.reset(other.cubic_reflection_texture->Clone());
}

animate::Node * Shader::FindAnimateNode(const Atom & name, bool recursion)
{
	if( diffuse_texture )
	{
		if( diffuse_texture->GetName() == name )
			return diffuse_texture.get();
		if( recursion )
		{
			animate::Node * node = diffuse_texture->FindAnimateNode( name, recursion );
			if ( node )
				return node;
		}
	}
	if( bump_texture )
	{
		if( bump_texture->GetName() == name )
			return bump_texture.get();
		if( recursion )
		{
			animate::Node * node = bump_texture->FindAnimateNode( name, recursion );
			if ( node )
				return node;
		}
	}
	if( emissive_sequence )
	{
		if( emissive_sequence->GetName() == name )
			return emissive_sequence.get();
		if( recursion )
		{
			animate::Node * node = emissive_sequence->FindAnimateNode( name, recursion );
			if ( node )
				return node;
		}
	}
	if( emissive_texture )
	{
		if( emissive_texture->GetName() == name )
			return emissive_texture.get();
		if( recursion )
		{
			animate::Node * node = emissive_texture->FindAnimateNode( name, recursion );
			if ( node )
				return node;
		}
	}
	if( cubic_reflection_texture )
	{
		if( cubic_reflection_texture->GetName() == name )
			return cubic_reflection_texture.get();
		if( recursion )
		{
			animate::Node * node = cubic_reflection_texture->FindAnimateNode( name, recursion );
			if ( node )
				return node;
		}
	}
	return 0;
}

math::Vector3<float> Shader::GetDiffuse()
{
	return material.diffuse;
}

void Shader::SetDiffuse(math::Vector3<float> value)
{
	material.diffuse = value;
}

math::Vector3<float> Shader::GetReflection()
{
	return material.reflection;
}

void Shader::SetReflection(math::Vector3<float> value)
{
	material.reflection = value;
}

math::Vector3<float> Shader::GetEmissive()
{
	return material.emissive;
}

void Shader::SetEmissive(const math::Vector3<float> value)
{
	material.emissive = value;
}

bool Shader::on_node(xml::Provider * prov, const std::string & name )
{
	if ( name == "Material" )
	{
		return prov->parse_object( &material );
	}
	if ( name == "Diffuse" && !diffuse_texture )
	{
		TextureSimple::Name nam;
		if( !prov->parse_object( &nam) )
			return false;
		diffuse_texture.reset(new TextureSimple(diffuse_texture_name, nam));
		return true;
	}
	if ( name == "DiffuseRenderTarget" && !diffuse_texture )
	{
		TextureRenderTarget::Name nam;
		if( !prov->parse_object( &nam) )
			return false;
		diffuse_texture.reset(new TextureRenderTarget(diffuse_texture_name, nam));
		return true;
	}
	if ( name == "Bump" && !bump_texture )
	{
		TextureSimple::Name nam;
		if( !prov->parse_object( &nam) )
			return false;
		bump_texture.reset(new TextureSimple(bump_texture_name, nam));
		return true;
	}
	if ( name == "BumpRenderTarget" && !bump_texture )
	{
		TextureRenderTarget::Name nam;
		if( !prov->parse_object( &nam) )
			return false;
		bump_texture.reset(new TextureRenderTarget(bump_texture_name, nam));
		return true;
	}
	if ( name == "Reflection" && !cubic_reflection_texture )
	{
		TextureReflection::Name nam;
		if( !prov->parse_object( &nam) )
			return false;
		cubic_reflection_texture.reset(new TextureReflection(cubic_reflection_texture_name, nam));
		return true;
	}
/*	if ( name == "ReflectionRenderTarget" && !cubic_reflection_texture )
	{
		TextureRenderTarget::Name nam;
		if( !prov->parse_object( &nam) )
			return false;
		cubic_reflection_texture = new TextureRenderTarget(cubic_reflection_texture_name, nam);
		return true;
	}*/
	if ( name == "Emissive" && !emissive_texture )
	{
		TextureParser texture_parser(emissive_texture_name);

		if( !prov->parse_object( &texture_parser) )
			return false;

		if(texture_parser.texture_sequence)
			emissive_sequence.reset(texture_parser.texture_sequence);
		else
			emissive_texture.reset(new TextureSimple(emissive_texture_name, texture_parser.texture_simple));

		return true;
	}
	return false;
}

bool Shader::on_attribute(xml::Provider *prov, const std::string & name, const std::string & value )
{
	if( name == "Name" )
	{
		Atom tmpName;
		if (!converter::convert(value, &tmpName))
			return false;
		this->SetName(tmpName);
		return true;
	}
	if( name == "EmissiveAlphaMultiply" )
	{
		return converter::convert( value, &SuperPuperMegaEmissiveAlphaMutilplyByAlphaBug );
	}
	if( name == "TrianglesCount" )
	{
		return converter::convert( value, &triangles_count );
	}
	if( name == "VertexCount" )
	{
		return converter::convert( value, &vertex_count );
	}
	return false;
}

void Shader::on_save_attributes( xml::SaverBase * saver )const
{
	saver->save_attribute("Name", GetName());

	if(triangles_count != 0)
		saver->save_attribute("TrianglesCount", triangles_count);

	if(vertex_count != 0)
		saver->save_attribute("VertexCount", vertex_count);
}

void Shader::on_save_nodes( xml::SaverBase * saver )const
{
	saver->save_node("Material", &material);
}

void Shader::RandomizeCommand()
{
	material.emissive = Color(1,1,1) - material.emissive;
}

void Shader::render_to_list( hard::Hardware * hardware, hard::RenderMaterial * render_material )
{
	render_material->diffuse = material.diffuse;
	render_material->specular = material.specular;
	render_material->specular_power = material.specular_power;
	render_material->emissive = material.emissive;
	render_material->transparency = material.transparency;
	render_material->reflection = material.reflection;
//	render_material->reflectivity = material.reflectivity;

	if( SuperPuperMegaEmissiveAlphaMutilplyByAlphaBug )
	{
		render_material->emissive *= (1 - material.transparency);
		render_material->reflection *= (1 - material.transparency);
	}

	if( diffuse_texture )
		diffuse_texture->render_to_list( hardware, &render_material->diffuse_texture );

	if(emissive_sequence)
		emissive_sequence->render_to_list( hardware, &render_material->emissive_texture );
	else
	if( emissive_texture )
		emissive_texture->render_to_list( hardware, &render_material->emissive_texture );

#ifndef IPHONE_VERSION
	if( bump_texture )
		bump_texture->render_to_list( hardware, &render_material->bump_texture );
	if( cubic_reflection_texture )
		cubic_reflection_texture->render_to_list( hardware, &render_material->cubic_reflection_texture );
#endif
}


} // namespace material
} // namespace lib3d
} // namespace liba

